﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Data;

using Uber.Collections;
using Uber.HabboHotel.Pets;
using Uber.HabboHotel.RoomBots;
using Uber.HabboHotel.Pathfinding;
using Uber.HabboHotel.Items;
using Uber.HabboHotel.GameClients;
using Uber.Messages;
using Uber.Storage;
using System.Collections;

namespace Uber.HabboHotel.Rooms
{
    class Room
    {
        private uint Id;

        public string Name;
        public string Description;
        public string Type;
        public string Owner;
        public string Password;
        public int Category;
        public int State;
        public int UsersNow;
        public int UsersMax;
        public string ModelName;
        public string CCTs;
        public int Score;
        public SafeList<string> Tags;
        public bool AllowPets;
        public bool AllowPetsEating;
        public bool AllowWalkthrough;
        public bool Hidewall;
        public bool IsMuted;

        public SafeList<RoomUser> UserList;
        public int UserCounter = 0;

        private int IdleTime;

        public RoomIcon myIcon;

        public List<uint> UsersWithRights;
        private Dictionary<uint, Double> Bans;

        public RoomEvent Event;

        public string Wallpaper;
        public string Floor;
        public string Landscape;

        public SafeList<RoomItem> Items;
        public MoodlightData MoodlightData;

        public SafeList<Trade> ActiveTrades;

        public Dictionary<uint, int> UserEffects;

        public bool KeepAlive;

        public MatrixState[,] Matrix;
        public bool[,] UserMatrix;
        public Coord[,] BedMatrix;
        public double[,] HeightMatrix;
        public double[,] TopStackHeight;

        public Boolean HasOngoingEvent
        {
            get
            {
                return (Event != null);
            }
        }

        public RoomIcon Icon
        {
            get
            {
                return myIcon;
            }

            set
            {
                myIcon = value;
            }
        }

        public int UserCount
        {
            get
            {
                int i = 0;

                for (int e = 0; e < UserList.Count; e++)
                {
                    var User = UserList[e];

                    if (User == null || User.IsBot || User.IsSpectator || User.IsPet)
                    {
                        continue;
                    }

                    i++;
                }

                return i;
            }
        }

        public int TagCount
        {
            get
            {
                return Tags.Count;
            }
        }

        public RoomModel Model
        {
            get
            {
                return UberEnvironment.GetGame().GetRoomManager().GetModel(ModelName);
            }
        }

        public uint RoomId
        {
            get
            {
                return Id;
            }
        }

        public List<RoomItem> FloorItems
        {
            get
            {
                var FloorItems = new List<RoomItem>();

                for (int i = 0; i < this.Items.Count; i++)
                {
                    var Item = this.Items[i];

                    if (Item == null)
                    {
                        continue;
                    }

                    if (Item.IsFloorItem)
                    {
                        FloorItems.Add(Item);
                    }
                }

                return FloorItems.ToList();
            }
        }

        public List<RoomItem> WallItems
        {
            get
            {
                var WallItems = new List<RoomItem>();

                for (int i = 0; i < this.Items.Count; i++)
                {
                    var Item = this.Items[i];

                    if (Item == null)
                    {
                        continue;
                    }

                    if (Item.IsWallItem)
                    {
                        WallItems.Add(Item);
                    }
                }

                return WallItems.ToList();
            }
        }

        private int GetVirtualId
        {
            get
            {
                lock (_lock)
                {
                    UserCounter++;
                    return UserCounter;
                }
            }
        }

        public Boolean CanTradeInRoom
        {
            get
            {
                return (!IsPublic);
            }
        }

        public bool IsPublic
        {
            get
            {
                return (Type == "public");
            }
        }

        public int PetCount
        {
            get
            {
                int c = 0;

                for (int e = 0; e < this.UserList.Count; e++)
                {
                    var User = this.UserList[e];

                    if (User == null)
                    {
                        continue;
                    }

                    if (!User.IsPet)
                    {
                        c++;
                    }
                }

                return c;
            }
        }

        private readonly Object _lock;

        public Room(uint mId, string mName, string mDescription, string mType, string mOwner, int mCategory,
            int mState, int mUsersMax, string mModelName, string mCCTs, int mScore, SafeList<string> mTags, bool mAllowPets,
            bool mAllowPetsEating, bool mAllowWalkthrough, bool mHidewall, RoomIcon mIcon, string mPassword, string mWallpaper, string mFloor,
            string mLandscape)
        {
            // Start Information
            this.Id = mId;
            this.Name = mName;
            this.Description = mDescription;
            this.Owner = mOwner;
            this.Category = mCategory;
            this.Type = mType;
            this.State = mState;
            this.Score = mScore;
            this.ModelName = mModelName;
            this.CCTs = mCCTs;
            this.myIcon = mIcon;
            this.Password = mPassword;
            this.Wallpaper = mWallpaper;
            this.Floor = mFloor;
            this.Landscape = mLandscape;
            // End Information

            // Start Users
            this.UsersNow = 0;
            this.UserCounter = 0;
            this.UsersMax = mUsersMax;

            this.UserList = null;
            this.UserList = new SafeList<RoomUser>();
            this.UserMatrix = new bool[Model.MapSizeX, Model.MapSizeY];
            // End Users

            // Start Public
            this.Tags = mTags;
            this.Event = null;
            // End Public

            // Start Booleans
            this.AllowPets = mAllowPets;
            this.AllowPetsEating = mAllowPetsEating;
            this.AllowWalkthrough = mAllowWalkthrough;
            this.Hidewall = mHidewall;
            this.IsMuted = false;
            // End Booleans

            // Start Collections
            this.Bans = new Dictionary<uint, double>();
            this.Items = new SafeList<RoomItem>();
            this.ActiveTrades = new SafeList<Trade>();
            this.UserEffects = new Dictionary<uint, int>();

            // Start Room State
            this.IdleTime = 0;
            this.KeepAlive = true;
            // End Room State

            this._lock = new Object();

            lock (_lock)
            {
                this.LoadRights();
                this.LoadFurniture();
                this.GenerateMaps();

                if (IsPublic) { this.InitBots(); }
                this.InitPets();
            }

            UberEnvironment.GetLogging().WriteLine("Room -> [" + RoomId + "] " + Name + " loaded.", Core.LogLevel.Debug);
        }

        public void InitBots()
        {
            var Bots = UberEnvironment.GetGame().GetBotManager().GetBotsForRoom(RoomId);

            foreach (RoomBot Bot in Bots)
            {
                DeployBot(Bot);
            }
        }

        public void InitPets()
        {
            DataTable Data = null;

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                dbClient.AddParamWithValue("roomid", RoomId);
                Data = dbClient.ReadDataTable("SELECT * FROM user_pets WHERE room_id = @roomid");
            }

            if (Data == null)
            {
                return;
            }

            foreach (DataRow Row in Data.Rows)
            {
                Pet Pet = UberEnvironment.GetGame().GetCatalog().GeneratePetFromRow(Row);

                DeployBot(new RoomBot(Pet.PetId, RoomId, "pet", "freeroam", Pet.Name, "", Pet.Look, Pet.X, Pet.Y, (int)Pet.Z, 0, 0, 0, 0, 0), Pet);
            }
        }

        public RoomUser DeployBot(RoomBot Bot)
        {
            return DeployBot(Bot, null);
        }

        public RoomUser DeployBot(RoomBot Bot, Pet PetData)
        {
            RoomUser BotUser = new RoomUser(0, RoomId, GetVirtualId);

            if ((Bot.X > 0 && Bot.Y > 0) && Bot.X <= Model.MapSizeX && Bot.Y <= Model.MapSizeY)
            {
                BotUser.SetPos(Bot.X, Bot.Y, Bot.Z);
                BotUser.SetRot(Bot.Rot);
            }
            else
            {
                Bot.X = Model.DoorX;
                Bot.Y = Model.DoorY;

                BotUser.SetPos(Model.DoorX, Model.DoorY, Model.DoorZ);
                BotUser.SetRot(Model.DoorOrientation);
            }

            UserMatrix[Bot.X, Bot.Y] = true;

            BotUser.BotData = Bot;
            BotUser.BotAI = Bot.GenerateBotAI(BotUser.VirtualId);

            if (BotUser.IsPet)
            {
                BotUser.BotAI.Init(Convert.ToInt32(Bot.BotId), BotUser.VirtualId, RoomId);
                BotUser.PetData = PetData;
                BotUser.PetData.VirtualId = BotUser.VirtualId;
            }
            else
            {
                BotUser.BotAI.Init(-1, BotUser.VirtualId, RoomId);
            }

            UserList.Add(BotUser);

            UpdateUserStatus(BotUser);

            BotUser.UpdateNeeded = true;

            ServerMessage EnterMessage = new ServerMessage(28);
            EnterMessage.AppendBoolean(true);
            BotUser.Serialize(EnterMessage);
            SendMessage(EnterMessage);

            BotUser.BotAI.OnSelfEnterRoom();

            if (BotUser.IsPet)
            { // std / pla
                BotUser.AddStatus("gst sml", "");
            }

            return BotUser;
        }

        public void RemoveBot(int VirtualId, bool Kicked)
        {
            RoomUser User = GetRoomUserByVirtualId(VirtualId);

            if (User == null || !User.IsBot)
            {
                return;
            }

            User.BotAI.OnSelfLeaveRoom(Kicked);

            ServerMessage LeaveMessage = new ServerMessage(29);
            LeaveMessage.AppendRawInt32(User.VirtualId);
            SendMessage(LeaveMessage);

            UserMatrix[User.X, User.Y] = false;

            if (UserList.Contains(User))
            {
                UserList.Remove(User);
            }
        }

        public void OnUserSay(RoomUser User, string Message, bool Shout)
        {
            for(int i = 0; i < this.UserList.Count; i++)
            {
                RoomUser Usr = this.UserList[i];

                if (!Usr.IsBot)
                {
                    continue;
                }

                if (Shout)
                {
                    Usr.BotAI.OnUserShout(User, Message);
                }
                else if (!Shout)
                {
                    Usr.BotAI.OnUserSay(User, Message);
                }
            }
        }

        public void RegenerateUserMatrix()
        {
            this.UserMatrix = new bool[Model.MapSizeX, Model.MapSizeY];

            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                this.UserMatrix[User.X, User.Y] = true;
            }
        }

        public void GenerateMaps()
        {
            // Create matrix arrays
            Matrix = new MatrixState[Model.MapSizeX, Model.MapSizeY];
            BedMatrix = new Coord[Model.MapSizeX, Model.MapSizeY];
            HeightMatrix = new double[Model.MapSizeX, Model.MapSizeY];
            TopStackHeight = new double[Model.MapSizeX, Model.MapSizeY];

            // Fill in the basic data based purely on the heightmap
            for (int line = 0; line < Model.MapSizeY; line++)
            {
                for (int chr = 0; chr < Model.MapSizeX; chr++)
                {
                    Matrix[chr, line] = MatrixState.BLOCKED;
                    BedMatrix[chr, line] = new Coord(chr, line);
                    HeightMatrix[chr, line] = 0;
                    TopStackHeight[chr, line] = 0.0;

                    if (chr == Model.DoorX && line == Model.DoorY)
                    {
                        Matrix[chr, line] = MatrixState.WALKABLE_LASTSTEP;
                    }
                    else if (Model.SqState[chr, line] == SquareState.OPEN)
                    {
                        Matrix[chr, line] = MatrixState.WALKABLE;
                    }
                    else if (Model.SqState[chr, line] == SquareState.SEAT)
                    {
                        Matrix[chr, line] = MatrixState.WALKABLE_LASTSTEP;
                    }
                }
            }

            // Loop through the items in the room
            for(int i = 0; i < Items.Count; i++)
            {
                RoomItem Item = Items[i];

                // If we're dealing with anything other than a floor item, skip
                if (!Item.IsFloorItem)
                {
                    continue;
                }

                // If this is a rug, ignore it.
                if (Item.GetBaseItem().Height <= 0)
                {
                    continue;
                }

                // Make sure we're the highest item here!
                if (TopStackHeight[Item.X, Item.Y] <= Item.Z)
                {
                    TopStackHeight[Item.X, Item.Y] = Item.Z;

                    // If this item is walkable and on the floor, allow users to walk here.
                    if (Item.GetBaseItem().Walkable)
                    {
                        Matrix[Item.X, Item.Y] = MatrixState.WALKABLE;
                        HeightMatrix[Item.X, Item.Y] = Item.GetBaseItem().Height;
                    }
                    // If this item is a gate, open, and on the floor, allow users to walk here.
                    else if (Item.Z <= (Model.SqFloorHeight[Item.X, Item.Y] + 0.1) && Item.GetBaseItem().InteractionType.ToLower() == "gate" && Item.ExtraData == "1")
                    {
                        Matrix[Item.X, Item.Y] = MatrixState.WALKABLE;
                    }
                    // If this item is a set or a bed, make it's square walkable (but only if last step)
                    else if (Item.GetBaseItem().IsSeat || Item.GetBaseItem().InteractionType.ToLower() == "bed")
                    {
                        Matrix[Item.X, Item.Y] = MatrixState.WALKABLE_LASTSTEP;
                    }
                    // Finally, if it's none of those, block the square.
                    else
                    {
                        Matrix[Item.X, Item.Y] = MatrixState.BLOCKED;
                    }
                }

                var Points = GetAffectedTiles(Item.GetBaseItem().Length, Item.GetBaseItem().Width, Item.X, Item.Y, Item.Rot);

                if (Points == null)
                {
                    Points = new Dictionary<int, AffectedTile>();
                }

                foreach (AffectedTile Tile in Points.Values)
                {
                    // Make sure we're the highest item here!
                    if (TopStackHeight[Tile.X, Tile.Y] <= Item.Z)
                    {
                        TopStackHeight[Tile.X, Tile.Y] = Item.Z;

                        // If this item is walkable and on the floor, allow users to walk here.
                        if (Item.GetBaseItem().Walkable)
                        {
                            Matrix[Tile.X, Tile.Y] = MatrixState.WALKABLE;
                            HeightMatrix[Tile.X, Tile.Y] = Item.GetBaseItem().Height;
                        }
                        // If this item is a gate, open, and on the floor, allow users to walk here.
                        else if (Item.Z <= (Model.SqFloorHeight[Item.X, Item.Y] + 0.1) && Item.GetBaseItem().InteractionType.ToLower() == "gate" && Item.ExtraData == "1")
                        {
                            Matrix[Tile.X, Tile.Y] = MatrixState.WALKABLE;
                        }
                        // If this item is a set or a bed, make it's square walkable (but only if last step)
                        else if (Item.GetBaseItem().IsSeat || Item.GetBaseItem().InteractionType.ToLower() == "bed")
                        {
                            Matrix[Tile.X, Tile.Y] = MatrixState.WALKABLE_LASTSTEP;
                        }
                        // Finally, if it's none of those, block the square.
                        else
                        {
                            Matrix[Tile.X, Tile.Y] = MatrixState.BLOCKED;
                        }
                    }

                    // Set bad maps
                    if (Item.GetBaseItem().InteractionType.ToLower() == "bed")
                    {
                        if (Item.Rot == 0 || Item.Rot == 4)
                        {
                            BedMatrix[Tile.X, Tile.Y].y = Item.Y;
                        }

                        if (Item.Rot == 2 || Item.Rot == 6)
                        {
                            BedMatrix[Tile.X, Tile.Y].x = Item.X;
                        }
                    }
                }
            }
        }

        public void LoadRights()
        {
            this.UsersWithRights = null;
            this.UsersWithRights = new List<uint>();

            DataTable Data = null;

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                Data = dbClient.ReadDataTable("SELECT user_id FROM room_rights WHERE room_id = '" + Id + "'");
            }

            if (Data == null)
            {
                return;
            }

            foreach (DataRow Row in Data.Rows)
            {
                this.UsersWithRights.Add((uint)Row["user_id"]);
            }
        }

        public void LoadFurniture()
        {
            this.Items = null;
            this.Items = new SafeList<RoomItem>();

            DataTable Data = null;

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                Data = dbClient.ReadDataTable("SELECT * FROM items WHERE room_id = '" + Id + "'");
            }

            if (Data == null)
            {
                return;
            }

            foreach (DataRow Row in Data.Rows)
            {
                RoomItem Item = new RoomItem((uint)Row["id"], RoomId, (uint)Row["base_item"], (string)Row["extra_data"],
                    (int)Row["x"], (int)Row["y"], (Double)Row["z"], (int)Row["rot"], (string)Row["wall_pos"], (int)Row["fw_count"]);

                if (Item.GetBaseItem().InteractionType.ToLower() == "dimmer" && this.MoodlightData == null)
                {
                    MoodlightData = new MoodlightData(Item.Id);
                }

                if (Item.GetBaseItem().InteractionType.ToLower() == "firework")
                {
                    Item.Interactor.OnPlace(null, Item);
                }

                this.Items.Add(Item);
            }
        }

        public Boolean CheckRights(GameClient Session)
        {
            return CheckRights(Session, false);
        }

        public Boolean CheckRights(GameClient Session, bool RequireOwnership)
        {
            if (Session.GetHabbo().Username.ToLower() == Owner.ToLower())
            {
                return true;
            }

            if (Session.GetHabbo().HasFuse("fuse_admin") || Session.GetHabbo().HasFuse("fuse_any_room_controller"))
            {
                return true;
            }

            if (!RequireOwnership)
            {
                if (Session.GetHabbo().HasFuse("fuse_any_room_rights"))
                {
                    return true;
                }

                if (UsersWithRights.Contains(Session.GetHabbo().Id))
                {
                    return true;
                }
            }

            return false;
        }

        public RoomItem GetItem(uint Id)
        {
            for (int i = 0; i < Items.Count; i++)
            {
                RoomItem Item = Items[i];

                if (Item.Id == Id)
                {
                    return Item;
                }
            }

            return null;
        }

        public void RemoveFurniture(GameClient Session, uint Id, bool Delete)
        {
            RoomItem Item = GetItem(Id);

            if (Item == null)
            {
                return;
            }

            Item.Interactor.OnRemove(Session, Item);

            if (Item.IsWallItem)
            {
                ServerMessage Message = new ServerMessage(84);
                Message.AppendRawUInt(Item.Id);
                Message.AppendBreak();
                Message.AppendBoolean(false);
                SendMessage(Message);
            }
            else if (Item.IsFloorItem)
            {
                ServerMessage Message = new ServerMessage(94);
                Message.AppendRawUInt(Item.Id);
                Message.AppendBreak();
                Message.AppendBoolean(false);
                SendMessage(Message);
            }

            Items.Remove(Item);

            if (Delete)
            {
                using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
                {
                    dbClient.ExecuteQuery("DELETE FROM items WHERE id = '" + Id + "' LIMIT 1");
                }
            }
            else
            {
                using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
                {
                    dbClient.ExecuteQuery("UPDATE items SET room_id = '0', x = '0', y = '0', z = '0', rot = '0', wall_pos = '', user_id = '" + Session.GetHabbo().Id + "' WHERE id = '" + Id + "' LIMIT 1");
                }
            }

            this.GenerateMaps();
            this.UpdateUserStatusses();
        }

        public bool CanWalk(int X, int Y, Double Z, Boolean LastStep, GameClient Session)
        {
            if (Session != null && GetRoomUserByHabbo(Session.GetHabbo().Username) != null && GetRoomUserByHabbo(Session.GetHabbo().Username).AllowOverride) { return true; }

            if (X < 0 || X >= Model.MapSizeX || Y >= Model.MapSizeY || Y < 0)
            {
                return false;
            }

            if (SquareHasUsers(X, Y, LastStep))
            {
                return false;
            }

            if (Matrix[X, Y] == MatrixState.BLOCKED)
            {
                return false;
            }
            else if (Matrix[X, Y] == MatrixState.WALKABLE_LASTSTEP && !LastStep)
            {
                return false;
            }

            return true;
        }

        public RoomItem GetRoomItemOnTile(int X, int Y)
        {
            for (int i = 0; i < FloorItems.Count; i++)
            {
                RoomItem Item = FloorItems[i]; if (Item == null) { continue; }
                if (Item.X == X && Item.Y == Y) { return Item; }
            }

            return null;
        }

        public void ProcessRoom()
        {
            int i = 0;

            // Loop through all furni and process them if they want to be processed
            for (int iu = 0; iu < Items.Count; iu++)
            {
                RoomItem Item = Items[iu];

                if (!Item.UpdateNeeded)
                {
                    continue;
                }

                Item.ProcessUpdates();
            }

            // Loop through all users and bots and process them
            var ToRemove = new List<RoomUser>();

            for (int ie = 0; ie < UserList.Count; ie++)
            {
                RoomUser User = UserList[ie];

                if (User == null)
                {
                    ToRemove.Add(User);
                    continue;
                }

                User.IdleTime++;

                if (!User.IsAsleep && User.IdleTime >= 600)
                {
                    User.IsAsleep = true;

                    ServerMessage FallAsleep = new ServerMessage(486);
                    FallAsleep.AppendInt32(User.VirtualId);
                    FallAsleep.AppendBoolean(true);
                    SendMessage(FallAsleep);
                }

                if (User.NeedsAutokick && !ToRemove.Contains(User))
                {
                    ToRemove.Add(User);
                }

                if (User.CarryItemID > 0)
                {
                    User.CarryTimer--;

                    if (User.CarryTimer <= 0)
                    {
                        User.CarryItem(0);
                    }
                }

                bool invalidSetStep = false;

                if (User.SetStep)
                {
                    if (CanWalk(User.SetX, User.SetY, 0, true, User.GetClient()) || User.AllowOverride)
                    {
                        UserMatrix[User.X, User.Y] = false;

                        User.X = User.SetX;
                        User.Y = User.SetY;
                        User.Z = User.SetZ;

                        UserMatrix[User.X, User.Y] = true;

                        UpdateUserStatus(User);
                    }
                    else
                    {
                        invalidSetStep = true;
                    }

                    User.SetStep = false;
                }

                if (User.PathRecalcNeeded)
                {
                    PathFinder PathFinder = new PathFinder(Matrix, HeightMatrix, Model.MapSizeX, Model.MapSizeY);

                    User.GoalX = User.PathRecalcX;
                    User.GoalY = User.PathRecalcY;

                    Coord CurrentPos = new Coord(User.X, User.Y);
                    Coord Goal = new Coord(User.GoalX, User.GoalY);

                    List<PathFinderNode> Route = PathFinder.FindPath(CurrentPos, Goal);

                    if (Route != null)
                    {
                        Route.Remove(Route[0]);
                        User.Path = Route;

                        User.IsWalking = true;
                        User.PathRecalcNeeded = false;
                    }
                    else
                    {
                        User.PathRecalcNeeded = false;
                        User.Path.Clear();
                    }
                }

                if (User.IsWalking)
                {
                    if (invalidSetStep || User.Path.Count < 1 || User.GoalX == User.X && User.Y == User.GoalY)
                    {
                        User.Path.Clear();
                        User.IsWalking = false;

                        User.RemoveStatus("mv");

                        User.PathRecalcNeeded = false;

                        if (User.X == Model.DoorX && User.Y == Model.DoorY && !ToRemove.Contains(User) && !User.IsBot)
                        {
                            ToRemove.Add(User);
                        }

                        UpdateUserStatus(User);
                    }
                    else
                    {
                        WorkBestTile(User);
                        PathFinderNode NextStep = User.Path[0];
                        User.Path.Remove(NextStep);

                        int nextX = NextStep.X;
                        int nextY = NextStep.Y;

                        User.RemoveStatus("mv");

                        bool LastStep = false;

                        if (nextX == User.GoalX && nextY == User.GoalY)
                        {
                            LastStep = true;
                        }

                        if (CanWalk(nextX, nextY, 0, LastStep, User.GetClient()) || User.AllowOverride)
                        {
                            double nextZ = SqAbsoluteHeight(nextX, nextY);

                            User.Statusses.Remove("lay");
                            User.Statusses.Remove("sit");
                            User.AddStatus("mv", nextX + "," + nextY + "," + nextZ.ToString().Replace(',', '.'));

                            int newRot;

                            if (User.WalkBackwards)
                            {
                                newRot = Rotation.CalculateBackwards(User.X, User.Y, nextX, nextY);
                            }
                            else
                            {
                                newRot = Rotation.Calculate(User.X, User.Y, nextX, nextY);
                            }

                            User.RotBody = newRot;
                            User.RotHead = newRot;

                            User.SetStep = true;
                            User.SetX = BedMatrix[nextX, nextY].x;
                            User.SetY = BedMatrix[nextX, nextY].y;
                            User.SetZ = nextZ;
                        }
                        else
                        {
                            User.IsWalking = false;
                        }
                    }

                    User.UpdateNeeded = true;
                }
                else
                {
                    if (User.Statusses.ContainsKey("mv"))
                    {
                        User.RemoveStatus("mv");
                        User.UpdateNeeded = true;
                    }
                }

                if (User.IsBot)
                {
                    User.BotAI.OnTimerTick();
                }
                else
                {
                    i++;
                }
            }

            foreach (RoomUser toRemove in ToRemove)
            {
                RemoveUserFromRoom(toRemove.GetClient(), true, false);
            }

            ToRemove = null;

            // Update idle time
            if (i >= 1)
            {
                this.IdleTime = 0;
            }
            else
            {
                this.IdleTime++;
            }

            // If room has been idle for a while
            if (this.IdleTime >= 60 && this.KeepAlive)
            {
                UberEnvironment.GetGame().GetRoomManager().RequestRoomUnload(RoomId);
            }

            ServerMessage Updates = SerializeStatusUpdates(false);

            if (Updates != null)
            {
                SendMessage(Updates);
            }
        }

        public RoomUser GetUserByTile(Coord Coord)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i]; if (User == null) { continue; }

                if (User.Coordinate == Coord) { return User; }
            }

            return null;
        }

        private void WorkBestTile(RoomUser User)
        {
            if (User == null)
            {
                return;
            }

            if (User.Path.Count > 1)
            {
                PathFinderNode skipNode = User.Path[1];

                if (TilesTouching(User.X, User.Y, skipNode.X, skipNode.Y))
                {
                    if (CanWalk(skipNode.X, skipNode.Y, 0, false, User.GetClient()))
                    {
                        // Work Diagonal
                        int checkX1 = 0;
                        int checkX2 = 0;

                        if (skipNode.X > User.X)
                        {
                            checkX1 = -1;
                            checkX2 = 1;
                        }
                        else
                        {
                            checkX1 = 1;
                            checkX2 = -1;
                        }

                        if (CanWalk(skipNode.X + checkX1, skipNode.Y, 0, false, User.GetClient()) && CanWalk(User.X + checkX2, User.Y, 0, false, User.GetClient()))
                        {
                            PathFinderNode nextNode = User.Path[0];
                            User.Path.Remove(nextNode);
                        }
                    }
                }
            }
        }

        #region User handling (General)
        public void AddUserToRoom(GameClient Session, bool Spectator)
        {
            if (Session == null) { return; }

            RoomUser User = new RoomUser(Session.GetHabbo().Id, RoomId, GetVirtualId);

            if (Spectator)
            {
                User.IsSpectator = true;
            }
            else
            {
                User.SetPos(Model.DoorX, Model.DoorY, Model.DoorZ);
                User.SetRot(Model.DoorOrientation);

                if (CheckRights(Session, true))
                {
                    User.AddStatus("flatcrtl", "useradmin");
                }
                else if (CheckRights(Session))
                {
                    User.AddStatus("flatcrtl", "");
                }

                if (!User.IsBot && User.GetClient().GetHabbo().IsTeleporting)
                {
                    RoomItem Item = GetItem(User.GetClient().GetHabbo().TeleporterId);

                    if (Item != null)
                    {
                        User.SetPos(Item.X, Item.Y, Item.Z);
                        User.SetRot(Item.Rot);

                        Item.InteractingUser2 = Session.GetHabbo().Id;
                        Item.ExtraData = "2";
                        Item.UpdateState(false, true);
                    }
                }

                User.GetClient().GetHabbo().IsTeleporting = false;
                User.GetClient().GetHabbo().TeleporterId = 0;

                ServerMessage EnterMessage = new ServerMessage(28);
                EnterMessage.AppendBoolean(true);
                User.Serialize(EnterMessage);
                SendMessage(EnterMessage);
            }

            UserList.Add(User);

            Session.GetHabbo().OnEnterRoom(Id);

            if (!Spectator)
            {
                UpdateUserCount();

                for (int i = 0; i < UserList.Count; i++)
                {
                    RoomUser Usr = UserList[i];

                    if (!Usr.IsBot)
                    {
                        continue;
                    }

                    Usr.BotAI.OnUserEnterRoom(User);
                }
            }
        }

        public void RemoveUserFromRoom(GameClient Session, bool NotifyClient, bool NotifyKick)
        {
            // Checking Start
            if (Session == null)
            {
                return; // nothing to do with an empty gameclient
            }

            RoomUser ReceivedUser = GetRoomUserByHabbo(Session.GetHabbo().Id);

            if (ReceivedUser == null)
            {
                return; // already removed
            }

            if (!UserList.Contains(ReceivedUser))
            {
                return; // Theres no fking user!
            }
            // Checking Ends

            // Send a message
            if (NotifyClient)
            {
                if (NotifyKick)
                {
                    Session.GetMessageHandler().GetResponse().Init(33);
                    Session.GetMessageHandler().GetResponse().AppendInt32(4008);
                    Session.GetMessageHandler().SendResponse();
                }

                Session.GetMessageHandler().GetResponse().Init(18);
                Session.GetMessageHandler().SendResponse();
            }

            if (!ReceivedUser.IsSpectator)
            {
                // The tile will be unlocked
                this.UserMatrix[ReceivedUser.X, ReceivedUser.Y] = false;

                // Send Leave Message
                ServerMessage Message = new ServerMessage(29);
                Message.AppendRawInt32(ReceivedUser.VirtualId);
                SendMessage(Message);

                // Handle Actions that where done
                if (Session.GetHabbo() != null)
                {
                    if (HasActiveTrade(Session.GetHabbo().Id))
                    {
                        TryStopTrade(Session.GetHabbo().Id);
                    }
                    if ((Session.GetHabbo().Username.ToLower() == Owner.ToLower()) && HasOngoingEvent)
                    {
                        // Kills Event
                        ServerMessage KillEvent = new ServerMessage(370);
                        KillEvent.AppendStringWithBreak("-1");
                        SendMessage(KillEvent);

                        Event = null;
                    }

                    Session.GetHabbo().OnLeaveRoom();
                }

                // Removes Pets that has been planted.
                if (Session.GetHabbo() != null)
                {
                    for (int i = 0; i < UserList.Count; i++)
                    {
                        RoomUser User = UserList[i]; if (User == null || !User.IsPet) { continue; }

                        if (User.PetData.OwnerId == Session.GetHabbo().Id && Owner != Session.GetHabbo().Username)
                        {
                            User.BotAI.OnUserLeaveRoom(Session);

                            Session.GetHabbo().GetInventoryComponent().AddPet(User.PetData);
                            RemoveBot(User.VirtualId, false);
                        }
                    }
                }
            }

            if (UserList.Contains(ReceivedUser))
            {
                UserList.Remove(ReceivedUser);
            }

            ReceivedUser = null; // clean up user :D

            // Updates Count
            UpdateUserCount(); // waarom this. ervoor? idk? doe ik soms :D
        }

        public RoomUser GetPet(uint PetId)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (User.IsBot && User.IsPet && User.PetData != null && User.PetData.PetId == PetId)
                {
                    return User;
                }
            }

            return null;
        }

        public bool RoomContainsPet(uint PetId)
        {
            return (GetPet(PetId) != null);
        }

        public void UpdateUserCount()
        {
            this.UsersNow = UserCount;

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                dbClient.ExecuteQuery("UPDATE rooms SET users_now = '" + this.UsersNow + "' WHERE id = '" + Id + "' LIMIT 1");
            }
        }

        public RoomUser GetRoomUserByVirtualId(int VirtualId)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (User.VirtualId == VirtualId)
                {
                    return User;
                }
            }

            return null;
        }

        public RoomUser GetRoomUserByHabbo(uint Id)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (User.IsBot || User == null)
                {
                    continue;
                }

                if (User.HabboId == Id)
                {
                    return User;
                }
            }

            return null;
        }

        public RoomUser GetRoomUserByHabbo(string Name)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (User.IsBot || User == null)
                {
                    continue;
                }

                if (User.GetClient().GetHabbo().Username.ToLower() == Name.ToLower())
                {
                    return User;
                }
            }

            return null;
        }
        #endregion

        #region Communication
        public void SendMessage(ServerMessage Message)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (User.IsBot || User.GetClient() == null)
                {
                    continue;
                }

                User.GetClient().SendMessage(Message);
            }
        }

        public void SendMessageToUsersWithRights(ServerMessage Message)
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (User.IsBot || User.GetClient() == null)
                {
                    continue;
                }

                if (!CheckRights(User.GetClient()))
                {
                    continue;
                }

                User.GetClient().SendMessage(Message);
            }
        }
        #endregion

        public void Destroy()
        {
            // Send kick message. No users should be left in this room, but if they are for whatever reason, they should leave.
            SendMessage(new ServerMessage(18));

            // Clear user list and dispose of the engine thread
            lock (_lock)
            {
                this.IdleTime = 0;
                this.KeepAlive = false;
                this.Items = null;
                this.ActiveTrades = null;
                this.UsersWithRights = null;
                this.Tags = null;
                this.UserMatrix = null;
                this.UsersNow = 0;
                this.UserEffects = null;
                this.UserCounter = 0;
                this.Event = null;
            }

            this.SavePetsClean();

            this.UserList = null;

            UberEnvironment.GetLogging().WriteLine("Room <- [" + RoomId + "] " + Name + " disposed.", Core.LogLevel.Debug);
        }

        internal void SavePetsClean()
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i]; if (User == null) { continue; }

                if (!User.IsPet) { continue; }

                using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
                {
                    dbClient.AddParamWithValue("x", User.X);
                    dbClient.AddParamWithValue("Y", User.Y);
                    dbClient.AddParamWithValue("z", Convert.ToString(User.Z).Replace(",","."));
                    dbClient.AddParamWithValue("petid", User.PetData.PetId);
                    dbClient.ExecuteQuery("UPDATE user_pets SET x = @x,y = @y,z = @z WHERE id = @petid LIMIT 1");
                }
            }
        }

        public ServerMessage SerializeStatusUpdates(Boolean All)
        {
            var Users = new List<RoomUser>();

            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                if (!All)
                {
                    if (!User.UpdateNeeded)
                    {
                        continue;
                    }

                    User.UpdateNeeded = false;
                }

                Users.Add(User);
            }

            if (Users.Count == 0)
            {
                return null;
            }

            ServerMessage Message = new ServerMessage(34);
            Message.AppendInt32(Users.Count);

            for (int i = 0; i < Users.Count; i++)
            {
                RoomUser User = Users[i];

                User.SerializeStatus(Message);
            }

            return Message;
        }

        #region Room Bans
        public Boolean UserIsBanned(uint Id)
        {
            return Bans.ContainsKey(Id);
        }

        public void RemoveBan(uint Id)
        {
            Bans.Remove(Id);
        }

        public void AddBan(uint Id)
        {
            Bans.Add(Id, UberEnvironment.GetUnixTimestamp());
        }

        public Boolean HasBanExpired(uint Id)
        {
            if (!UserIsBanned(Id))
            {
                return true;
            }

            Double diff = UberEnvironment.GetUnixTimestamp() - Bans[Id];

            if (diff > 900)
            {
                return true;
            }

            return false;
        }
        #endregion

        public int ItemCountByType(String InteractionType)
        {
            int i = 0;

            for (int ie = 0; ie < Items.Count; ie++)
            {
                RoomItem Item = Items[ie];
                if (Item.GetBaseItem().InteractionType.ToLower() == InteractionType.ToLower())
                {
                    i++;
                }
            }

            return i;
        }

        #region Trading
        public bool HasActiveTrade(RoomUser User)
        {
            if (User.IsBot || User == null)
            {
                return false;
            }

            return HasActiveTrade(User.GetClient().GetHabbo().Id);
        }

        public bool HasActiveTrade(uint UserId)
        {
            foreach (Trade Trade in ActiveTrades)
            {
                if (Trade.ContainsUser(UserId))
                {
                    return true;
                }
            }

            return false;
        }

        public Trade GetUserTrade(RoomUser User)
        {
            if (User.IsBot || User == null)
            {
                return null;
            }

            return GetUserTrade(User.GetClient().GetHabbo().Id);
        }

        public Trade GetUserTrade(uint UserId)
        {
            foreach (Trade Trade in ActiveTrades)
            {
                if (Trade.ContainsUser(UserId))
                {
                    return Trade;
                }
            }

            return null;
        }

        public void TryStartTrade(RoomUser UserOne, RoomUser UserTwo)
        {
            if (UserOne == null || UserTwo == null || UserOne.IsBot || UserTwo.IsBot || UserOne.IsTrading || UserTwo.IsTrading || HasActiveTrade(UserOne) || HasActiveTrade(UserTwo))
            {
                return;
            }

            ActiveTrades.Add(new Trade(UserOne.GetClient().GetHabbo().Id, UserTwo.GetClient().GetHabbo().Id, RoomId));
        }

        public void TryStopTrade(uint UserId)
        {
            Trade Trade = GetUserTrade(UserId);

            if (Trade == null)
            {
                return;
            }

            Trade.CloseTrade(UserId);
            ActiveTrades.Remove(Trade);
        }
        #endregion

        #region Furni handling and stacking
        public bool SetFloorItem(GameClient Session, RoomItem Item, int newX, int newY, int newRot, bool newItem)
        {
            // Find affected tiles
            var AffectedTiles = GetAffectedTiles(Item.GetBaseItem().Length, Item.GetBaseItem().Width, newX, newY, newRot);

            // Verify tiles are valid
            if (!ValidTile(newX, newY))
            {
                return false;
            }

            foreach (AffectedTile Tile in AffectedTiles.Values)
            {
                if (!ValidTile(Tile.X, Tile.Y))
                {
                    return false;
                }
            }

            // Start calculating new Z coordinate
            Double newZ = Model.SqFloorHeight[newX, newY];

            // Is the item trying to stack on itself!?
            if (Item.Rot == newRot && Item.X == newX && Item.Y == newY && Item.Z != newZ)
            {
                return false;
            }

            // Make sure this tile is open and there are no users here
            if (Model.SqState[newX, newY] != SquareState.OPEN)
            {
                return false;
            }

            foreach (AffectedTile Tile in AffectedTiles.Values)
            {
                if (Model.SqState[Tile.X, Tile.Y] != SquareState.OPEN)
                {
                    return false;
                }
            }

            // And that we have no users
            if (!Item.GetBaseItem().IsSeat)
            {
                if (SquareHasUsers(newX, newY))
                {
                    return false;
                }

                foreach (AffectedTile Tile in AffectedTiles.Values)
                {
                    if (SquareHasUsers(Tile.X, Tile.Y))
                    {
                        return false;
                    }
                }
            }

            // Find affected objects
            var ItemsOnTile = GetFurniObjects(newX, newY);
            var ItemsAffected = new List<RoomItem>();
            var ItemsComplete = new List<RoomItem>();

            foreach (AffectedTile Tile in AffectedTiles.Values)
            {
                List<RoomItem> Temp = GetFurniObjects(Tile.X, Tile.Y);

                if (Temp != null)
                {
                    ItemsAffected.AddRange(Temp);
                }
            }

            if (ItemsOnTile == null) ItemsOnTile = new List<RoomItem>();

            ItemsComplete.AddRange(ItemsOnTile);
            ItemsComplete.AddRange(ItemsAffected);

            // Check for items in the stack that do not allow stacking on top of them
            foreach (RoomItem I in ItemsComplete)
            {
                if (I.Id == Item.Id)
                {
                    continue;
                }

                if (!I.GetBaseItem().Stackable)
                {
                    return false;
                }
            }

            // If this is a rotating action, maintain item at current height
            if (Item.Rot != newRot && Item.X == newX && Item.Y == newY)
            {
                newZ = Item.Z;
            }

            // Are there any higher objects in the stack!?
            foreach (RoomItem I in ItemsComplete)
            {
                if (I.Id == Item.Id)
                {
                    continue; // cannot stack on self
                }

                if (I.TotalHeight > newZ)
                {
                    newZ = I.TotalHeight;
                }
            }

            // Verify the rotation is correct
            if (newRot != 0 && newRot != 2 && newRot != 4 && newRot != 6 && newRot != 8)
            {
                newRot = 0;
            }

            Item.X = newX;
            Item.Y = newY;
            Item.Z = newZ;
            Item.Rot = newRot;

            if (Item.Interactor.OnPlace(Session, Item) == false) return false;

            if (newItem)
            {
                using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
                {
                    dbClient.AddParamWithValue("extra_data", Item.ExtraData);
                    dbClient.ExecuteQuery("UPDATE items SET room_id = '" + RoomId + "', x = '" + Item.X + "', y = '" + Item.Y + "', z = '" + Item.Z.ToString().Replace(',','.') + "', rot = '" + Item.Rot + "', wall_pos = '' WHERE id = '" + Item.Id + "' LIMIT 1");
                }

                Items.Add(Item);

                ServerMessage Message = new ServerMessage(93);
                Item.Serialize(Message);
                SendMessage(Message);
            }
            else
            {
                using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
                {
                    dbClient.ExecuteQuery("UPDATE items SET x = '" + Item.X + "', y = '" + Item.Y + "', z = '" + Item.Z.ToString().Replace(',', '.') + "', rot = '" + Item.Rot + "', wall_pos = '' WHERE id = '" + Item.Id + "' LIMIT 1");
                }

                ServerMessage Message = new ServerMessage(95);
                Item.Serialize(Message);
                SendMessage(Message);
            }

            GenerateMaps();
            UpdateUserStatusses();

            return true;
        }

        public bool SetWallItem(GameClient Session, RoomItem Item)
        {
            Item.Interactor.OnPlace(Session, Item);

            switch (Item.GetBaseItem().InteractionType.ToLower())
            {
                case "dimmer":

                    if (MoodlightData == null)
                    {
                        MoodlightData = new MoodlightData(Item.Id);
                        Item.ExtraData = MoodlightData.GenerateExtraData();
                    }

                    break;
            }

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                dbClient.AddParamWithValue("extra_data", Item.ExtraData);
                dbClient.ExecuteQuery("UPDATE items SET room_id = '" + RoomId + "', x = '0', y = '0', z = '0', rot = '0', wall_pos = '" + Item.WallPos + "' WHERE id = '" + Item.Id + "' LIMIT 1");
            }

            Items.Add(Item);

            ServerMessage Message = new ServerMessage(83);
            Item.Serialize(Message);
            SendMessage(Message);

            return true;
        }
        #endregion

        public void UpdateUserStatusses()
        {
            for (int i = 0; i < UserList.Count; i++)
            {
                RoomUser User = UserList[i];

                UpdateUserStatus(User);
            }
        }

        public Boolean OpenPlace(Coord Coord)
        {
            if (Coord == null) { return false; } // invalid add all nothing to do :D
            if (Coord.x > this.Model.MapSizeX || Coord.y > this.Model.MapSizeY) { return false; } // outside map, no items and invalid?

            var List = this.FloorItems;

            for (int i = 0; i < List.Count; i++)
            {
                RoomItem Item = List[i];

                if (Item == null || Item.IsWallItem || Item.GetBaseItem().Walkable || Item.GetBaseItem().Stackable)
                {
                    continue;
                }

                if (Item.Coordinate == Coord)
                {
                    return false;
                }
            }

            return true;
        }

        public double SqAbsoluteHeight(int X, int Y)
        {
            var ItemsOnSquare = GetFurniObjects(X, Y);
            double HighestStack = 0;

            bool deduct = false;
            double deductable = 0.0;

            if (ItemsOnSquare == null)
            {
                ItemsOnSquare = new List<RoomItem>();
            }

            if (ItemsOnSquare != null)
            {
                foreach (RoomItem Item in ItemsOnSquare)
                {
                    if (Item.TotalHeight > HighestStack)
                    {
                        if (Item.GetBaseItem().IsSeat || Item.GetBaseItem().InteractionType.ToLower() == "bed")
                        {
                            deduct = true;
                            deductable = Item.GetBaseItem().Height;
                        }
                        else
                        {
                            deduct = false;
                        }

                        HighestStack = Item.TotalHeight;
                    }
                }
            }

            double floorHeight = Model.SqFloorHeight[X, Y];
            double stackHeight = HighestStack - Model.SqFloorHeight[X, Y];

            if (deduct)
            {
                stackHeight -= deductable;
            }

            if (stackHeight < 0)
            {
                stackHeight = 0;
            }

            return (floorHeight + stackHeight);
        }

        public void UpdateUserStatus(RoomUser User)
        {
            if (User.Statusses.ContainsKey("lay") || User.Statusses.ContainsKey("sit"))
            {
                User.Statusses.Remove("lay");
                User.Statusses.Remove("sit");
                User.UpdateNeeded = true;
            }

            double newZ = SqAbsoluteHeight(User.X, User.Y);

            if (newZ != User.Z)
            {
                User.Z = newZ;
                User.UpdateNeeded = true;
            }

            if (Model.SqState[User.X, User.Y] == SquareState.SEAT)
            {
                if (!User.Statusses.ContainsKey("sit"))
                {
                    User.Statusses.Add("sit", "1.0");
                }

                User.Z = Model.SqFloorHeight[User.X, User.Y];
                User.RotHead = Model.SqSeatRot[User.X, User.Y];
                User.RotBody = Model.SqSeatRot[User.X, User.Y];

                User.UpdateNeeded = true;
            }

            var ItemsOnSquare = GetFurniObjects(User.X, User.Y);

            if (ItemsOnSquare == null)
            {
                ItemsOnSquare = new List<RoomItem>();

                if (UserEffects.ContainsKey(User.HabboId))
                {
                    ApplyUserEffect(User, 0);
                }
            }

            foreach (RoomItem Item in ItemsOnSquare)
            {
                if (Item.GetBaseItem().IsSeat)
                {
                    if (!User.Statusses.ContainsKey("sit"))
                    {
                        User.Statusses.Add("sit", Item.GetBaseItem().Height.ToString().Replace(',', '.'));
                    }

                    User.Z = Item.Z;
                    User.RotHead = Item.Rot;
                    User.RotBody = Item.Rot;

                    User.UpdateNeeded = true;
                    ApplyUserEffect(User, 0);
                }
                else if (Item.GetBaseItem().InteractionType.ToLower() == "bed")
                {
                    if (!User.Statusses.ContainsKey("lay"))
                    {
                        User.Statusses.Add("lay", Item.GetBaseItem().Height.ToString().Replace(',', '.') + " null");
                    }

                    User.Z = Item.Z;
                    User.RotHead = Item.Rot;
                    User.RotBody = Item.Rot;

                    User.UpdateNeeded = true;
                    ApplyUserEffect(User, 0);
                }
                else if (Item.GetBaseItem().InteractionType.ToLower() == "water1" || Item.GetBaseItem().InteractionType.ToLower() == "water2")
                {
                    if (Item.GetBaseItem().Name.ToLower() == "bw_water_1")
                    {
                        ApplyUserEffect(User, 30);
                    }

                    if (Item.GetBaseItem().Name.ToLower() == "bw_water_2")
                    {
                        ApplyUserEffect(User, 29);
                    }

                    if (Item.GetBaseItem().Name.ToLower() == "hween10_pond")
                    {
                        ApplyUserEffect(User, 28);
                    }

                    if (User.GetClient().GetHabbo().CurrentQuestId == 19)
                    {
                        UberEnvironment.GetGame().GetQuestManager().UpdateQuest(19, User.GetClient());
                    }
                }
                else if (Item.GetBaseItem().Name.ToLower() == "es_skating_ice")
                {
                    if (User.GetClient().GetHabbo().Gender.ToLower() == "m")
                    {
                        ApplyUserEffect(User, 38);
                    }
                    else
                    {
                        ApplyUserEffect(User, 39);
                    }
                }
                else if (Item.GetBaseItem().InteractionType.ToLower() == "ball")
                {
                    // todo soccer :D
                }
                else
                {
                    ApplyUserEffect(User, 0);
                }
               
            }
        }

        public void ApplyUserEffect(RoomUser User, int EffectId)
        {
            if (UserEffects.ContainsKey(User.HabboId))
            {
                if (UserEffects[User.HabboId] == EffectId)
                {
                    return;
                }

                UserEffects.Remove(User.HabboId);
            }

            ServerMessage Message = new ServerMessage(485);
            Message.AppendInt32(User.VirtualId);
            Message.AppendInt32(EffectId);
            SendMessage(Message);
            User.UpdateNeeded = true;

            if (EffectId != 0)
            {
                UserEffects.Add(User.HabboId, EffectId);
            }
        }

        public void ApplyUserEffect(RoomUser User, int EffectId, bool Limit)
        {
            ServerMessage Message = new ServerMessage(485);
            Message.AppendInt32(User.VirtualId);
            Message.AppendInt32(EffectId);
            SendMessage(Message);
            User.UpdateNeeded = true;
        }

        public bool ValidTile(int X, int Y)
        {
            if (X < 0 || Y < 0 || X >= Model.MapSizeX || Y >= Model.MapSizeY)
            {
                return false;
            }

            return true;
        }

        public void TurnHeads(int X, int Y, uint SenderId) // Todo: fix glitch ? or habbos issue ?? idk O.O
        {
            /*foreach (RoomUser User in UserList)
            {
                if (User.HabboId == SenderId)
                {
                    continue;
                }

                User.SetRot(Rotation.Calculate(User.X, User.Y, X, Y), true);
            }*/
        }

        public List<RoomItem> GetFurniObjects(int X, int Y)
        {
            var Results = new List<RoomItem>();

            foreach (RoomItem Item in FloorItems)
            {
                if (Item.X == X && Item.Y == Y)
                {
                    Results.Add(Item);
                }

                var PointList = GetAffectedTiles(Item.GetBaseItem().Length, Item.GetBaseItem().Width, Item.X, Item.Y, Item.Rot);

                foreach (AffectedTile Tile in PointList.Values)
                {
                    if (Tile.X == X && Tile.Y == Y)
                    {
                        Results.Add(Item);
                    }
                }
            }

            if (Results.Count > 0)
            {
                return Results;
            }

            return null;
        }

        public RoomItem FindItem(uint Id)
        {
            for (int i = 0; i < Items.Count; i++)
            {
                RoomItem Item = Items[i];

                if (Item.Id == Id)
                {
                    return Item;
                }
            }

            return null;
        }

        public Dictionary<int, AffectedTile> GetAffectedTiles(int Length, int Width, int PosX, int PosY, int Rotation)
        {
            int x = 0;

            var PointList = new Dictionary<int, AffectedTile>();

            if (Length > 1)
            {
                if (Rotation == 0 || Rotation == 4)
                {
                    for (int i = 1; i < Length; i++)
                    {
                        PointList.Add(x++, new AffectedTile(PosX, PosY + i, i));

                        for (int j = 1; j < Width; j++)
                        {
                            PointList.Add(x++, new AffectedTile(PosX + j, PosY + i, (i < j) ? j : i));
                        }
                    }
                }
                else if (Rotation == 2 || Rotation == 6)
                {
                    for (int i = 1; i < Length; i++)
                    {
                        PointList.Add(x++, new AffectedTile(PosX + i, PosY, i));

                        for (int j = 1; j < Width; j++)
                        {
                            PointList.Add(x++, new AffectedTile(PosX + i, PosY + j, (i < j) ? j : i));
                        }
                    }
                }
            }

            if (Width > 1)
            {
                if (Rotation == 0 || Rotation == 4)
                {
                    for (int i = 1; i < Width; i++)
                    {
                        PointList.Add(x++, new AffectedTile(PosX + i, PosY, i));

                        for (int j = 1; j < Length; j++)
                        {
                            PointList.Add(x++, new AffectedTile(PosX + i, PosY + j, (i < j) ? j : i));
                        }
                    }
                }
                else if (Rotation == 2 || Rotation == 6)
                {
                    for (int i = 1; i < Width; i++)
                    {
                        PointList.Add(x++, new AffectedTile(PosX, PosY + i, i));

                        for (int j = 1; j < Length; j++)
                        {
                            PointList.Add(x++, new AffectedTile(PosX + j, PosY + i, (i < j) ? j : i));
                        }
                    }
                }
            }

            return PointList;
        }

        public bool SquareHasUsers(int X, int Y, bool LastStep)
        {
            if (AllowWalkthrough)
            {
                return false;
            }

            return SquareHasUsers(X, Y);
        }

        public bool SquareHasUsers(int X, int Y)
        {
            Coord Coord = BedMatrix[X, Y];
            return UserMatrix[Coord.x, Coord.y];
        }

        //This function is based on the one from "Holograph Emulator"
        public string WallPositionCheck(string wallPosition)
        {
            //:w=3,2 l=9,63 l
            try
            {
                if (wallPosition.Contains(Convert.ToChar(13)))
                { return null; }
                if (wallPosition.Contains(Convert.ToChar(9)))
                { return null; }

                string[] posD = wallPosition.Split(' ');
                if (posD[2] != "l" && posD[2] != "r")
                    return null;

                string[] widD = posD[0].Substring(3).Split(',');
                int widthX = int.Parse(widD[0]);
                int widthY = int.Parse(widD[1]);
                if (widthX < 0 || widthY < 0 || widthX > 200 || widthY > 200)
                    return null;

                string[] lenD = posD[1].Substring(2).Split(',');
                int lengthX = int.Parse(lenD[0]);
                int lengthY = int.Parse(lenD[1]);
                if (lengthX < 0 || lengthY < 0 || lengthX > 200 || lengthY > 200)
                    return null;

                return ":w=" + widthX + "," + widthY + " " + "l=" + lengthX + "," + lengthY + " " + posD[2];
            }
            catch
            {
                return null;
            }
        }

        public bool TilesTouching(int X1, int Y1, int X2, int Y2)
        {
            if (!(Math.Abs(X1 - X2) > 1 || Math.Abs(Y1 - Y2) > 1)) return true;
            if (X1 == X2 && Y1 == Y2) return true;
            return false;
        }

        public int TileDistance(int X1, int Y1, int X2, int Y2)
        {
            return Math.Abs(X1 - X2) + Math.Abs(Y1 - Y2);
        }
    }

    public class AffectedTile
    {
        int mX;
        int mY;
        int mI;

        public AffectedTile(int x, int y, int i)
        {
            mX = x;
            mY = y;
            mI = i;
        }

        public int X
        {
            get
            {
                return mX;
            }
        }

        public int Y
        {
            get
            {
                return mY;
            }
        }

        public int I
        {
            get
            {
                return mI;
            }
        }
    }
}
